/*
 * Copyright (C) 2010 TopCoder Inc., All Rights Reserved.
 */
package com.hercules.xre.protocol.testing.netty.ssl;
import com.hercules.xre.protocol.testing.Helper;
import com.hercules.xre.protocol.testing.netty.SSLEngineFactory;
import com.hercules.xre.protocol.testing.netty.SSLEngineFactoryException;
import com.topcoder.util.log.Log;
import com.topcoder.util.log.LogManager;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;

/**
 * <p>
 * Default SSLEngine factory that creates SSLEngine that uses passed keyStore for encryption.
 * </p>
 * <p>
 * <strong>Thread-safety:</strong> This class is thread safe as it is immutable after initialization.
 * </p>
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
*/
public class DefaultSSLEngineFactory implements SSLEngineFactory {
    /**
     * <p>
     * Logger that is used to log activities and errors.
     * It is initialized to LogManager.getLog(DefaultSSLEngineFactory.class.getName()).
     * Can not be null, never changes after initialization.
     * </p>
    */
    private static final Log LOGGER = LogManager.getLog(DefaultSSLEngineFactory.class.getName());
    /**
     * <p>
     * SSLContext that will be used for creation of SSLEngine's.
     * It is initialized in constructor and never changes after that.
     * </p>
    */
    private final SSLContext sslContext;
    /**
     * <p>
     * Creates new instance of a factory by creating and initializing SSLContext using passed
     * parameters. Please take a note that in current implementation every certificate will be
     * accepted as DummyTrustManager doesn't perform any validation.
     * </p>
     * @param protocol encryption protocol like "ssl", "tsl" or any other, can't be null, can't be empty
     * @param certificatePassword certificate password, can't be null, can't be empty
     * @param keyStorePath path to key store file, can't be null, can't be empty
     * @param keyStorePassword key store password, can't be null, can't be empty
     * @throws IllegalArgumentException when any of passed parameters is invalid
     * @throws SSLEngineFactoryException if any SSL Engine error occurs
    */
    public DefaultSSLEngineFactory(String protocol, String keyStorePath,
        String keyStorePassword, String certificatePassword) throws SSLEngineFactoryException {
        final String signature = "DefaultSSLEngineFactory#ctor";
        Helper.logEnter(signature, new String[] {"protocol", "keyStorePath", "keyStorePassword", "certificatePassword"},
                new Object[] {protocol, keyStorePath, keyStorePassword, certificatePassword}, LOGGER);
        try {
            Helper.checkString(protocol, "protocol", signature, LOGGER);
            Helper.checkString(keyStorePath, "keyStorePath", signature, LOGGER);
            Helper.checkString(keyStorePassword, "keyStorePassword", signature, LOGGER);
            Helper.checkString(certificatePassword, "certificatePassword", signature, LOGGER);
            String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
            if (algorithm == null) {
                algorithm = "SunX509";
            }
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray());
            // Set up key manager factory to use our key store
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
            kmf.init(ks, certificatePassword.toCharArray());
            // Initialize the SSLContext to work with our key managers
            sslContext = SSLContext.getInstance(protocol);
            TrustManager[] trustManagers = new TrustManager[] {new DummyTrustManager()};
            sslContext.init(kmf.getKeyManagers(), trustManagers, null);
        } catch (SecurityException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "security manager denies access to retrieve the ssl.KeyManagerFactory.algorithm property", e),
                    signature, LOGGER);
        } catch (KeyStoreException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "error occurs while retrieving KeyStore Instance", e), signature, LOGGER);
        } catch (NoSuchAlgorithmException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "can't found the algorithm ", e), signature, LOGGER);
        } catch (CertificateException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "any of the certificates in the keystore could not be loaded", e), signature, LOGGER);
        } catch (FileNotFoundException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "can't found the keyStorePath : " + keyStorePath, e), signature, LOGGER);
        } catch (IOException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "invalid keystore format or password", e), signature, LOGGER);
        } catch (UnrecoverableKeyException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "the key cannot be recovered", e), signature, LOGGER);
        } catch (KeyManagementException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                "key management error occurs", e), signature, LOGGER);
        } catch (IllegalStateException e) {
            throw Helper.logError(new SSLEngineFactoryException(
                 "the KeyManagerFactory is not initialized"), signature, LOGGER);
        } finally {
            Helper.logExit(signature, null, LOGGER);
        }
    }

    /**
     * <p>
     * Creates new instanec of SSLEngine using sslContext created in constructor.
     * </p>
     * @return SSLEngine that can be used for encryption.
    */
    public SSLEngine createEngine() {
        String signature = "DefaultSSLEngineFactory#createEngine";
        Helper.logEnter(signature, null, null, LOGGER);
        return Helper.logExit(signature, sslContext.createSSLEngine(), LOGGER);
    }
}

